﻿using System;
using System.Linq;
using Arroguella.Entities;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Arroguella.Tiles;
using Arroguella.Utilities;

namespace Arroguella.Panels
{
    public class GameMap : ScreenPanel
    {
        #region Constructor

        public GameMap(Game game, Guid seed)
            : base(game)
        {
            Arroguella = game as Arroguella;
            Seed = seed;
        }

        #endregion

        #region Events
        #endregion

        #region Methods

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            BackgroundColor = Color.DimGray;

            SetSizeAndShape(0, 0, 30, 10);

            base.Initialize();
        }

        // Makes a new map with the size and shape, based on the level
        public void MakeNewMap(int x, int y, int width, int height, int level, Player player)
        {
            BackgroundColor = Color.FromNonPremultiplied(75, 75, 75, 255);

            SetSizeAndShape(x, y, width, height);

            TileEngine = new TileEngine(Arroguella, SizeAndShape, Seed);
            TileEngine.GenerateMap(level);

            CombatEngine = new CombatEngine(Arroguella, TileEngine);

            // Set up the Player
            InitializePlayer(player);
        }

        /// <summary>
        /// Initializes the Player
        /// </summary>
        private void InitializePlayer(Player player)
        {
            Vector2 playerTilePosition = TileEngine.PlayerStartPosition;

            if (player == null)
            {
                Player = new Player();
                Player.Initialize(Arroguella.Font, playerTilePosition, SizeAndShape);
            }
            else
            {
                Player = player;
                Player.MoveTiles((int)(playerTilePosition.X - player.Coords.X), (int)(playerTilePosition.Y - player.Coords.Y));
            }        
            
            // Record in the tile engine that this tile is now occupied by the player
            TileEngine.TileMap[playerTilePosition].IsPlayerOccupied = true;
        }

        #region Player Movement

        /// <summary>
        /// Moves the player left a number of tiles.
        /// </summary>
        public void MovePlayerLeft(int tiles)
        {
            if (CanTileBeMovedTo(-tiles, 0))
            {
                Vector2 nextPlayerTileCoords = GetNextPlayerTileCoords(-tiles, 0);
                CreatureEntity creature = TryGetCreatureInTile(nextPlayerTileCoords);
                if (creature != null && creature.CurrentHealth > 0)
                {
                    CombatEngine.PerformRoundOfMeleeCombat(Player, creature);
                }
                else
                {
                    MovePlayer(-tiles, 0);
                }
            }
        }

        /// <summary>
        /// Moves the player right a number of tiles.
        /// </summary>
        public void MovePlayerRight(int tiles)
        {
            if (CanTileBeMovedTo(tiles, 0))
            {
                Vector2 nextPlayerTileCoords = GetNextPlayerTileCoords(tiles, 0);
                CreatureEntity creature = TryGetCreatureInTile(nextPlayerTileCoords);
                if (creature != null && creature.CurrentHealth > 0)
                {
                    CombatEngine.PerformRoundOfMeleeCombat(Player, creature);
                }
                else
                {
                    MovePlayer(tiles, 0);
                }
            }
        }

        /// <summary>
        /// Moves the player up a number of tiles.
        /// </summary>
        public void MovePlayerUp(int tiles)
        {
            if (CanTileBeMovedTo(0, -tiles))
            {
                Vector2 nextPlayerTileCoords = GetNextPlayerTileCoords(0, -tiles);
                CreatureEntity creature = TryGetCreatureInTile(nextPlayerTileCoords);
                if (creature != null && creature.CurrentHealth > 0)
                {
                    CombatEngine.PerformRoundOfMeleeCombat(Player, creature);
                }
                else
                {
                    MovePlayer(0, -tiles);
                }
            }
        }

        /// <summary>
        /// Moves the player down a number of tiles.
        /// </summary>
        public void MovePlayerDown(int tiles)
        {
            if(CanTileBeMovedTo(0, tiles))
            {
                Vector2 nextPlayerTileCoords = GetNextPlayerTileCoords(0, tiles);
                CreatureEntity creature = TryGetCreatureInTile(nextPlayerTileCoords);
                if (creature != null && creature.CurrentHealth > 0)
                {
                    CombatEngine.PerformRoundOfMeleeCombat(Player, creature);
                }
                else
                {
                    MovePlayer(0, tiles); 
                }
            }
        }

        /// <summary>
        /// Moves the player and adjust the tiles' IsPlayerOccupied flags.
        /// </summary>
        /// <param name="dx">Number of tiles to move in the x direction</param>
        /// <param name="dy">Number of tiles to move in the y direction</param>
        private void MovePlayer(int dx, int dy)
        {
            TileEngine.TileMap[Player.Coords].IsPlayerOccupied = false;
            Player.MoveTiles(dx, dy);
            TileEngine.TileMap[Player.Coords].IsPlayerOccupied = true; 
        }

        /// <summary>
        /// Whether or not a tile [dx, dy] tiles away can be moved to.
        /// </summary>
        /// <param name="dx">The number of tiles away from the player on the x-axis.</param>
        /// <param name="dy">The number of tiles away from the player on the y-axis.</param>
        /// <returns></returns>
        private bool CanTileBeMovedTo(int dx, int dy)
        {
            Vector2 tileCoords = GetNextPlayerTileCoords(dx, dy);

            bool canbeMoved = TileEngine.TileMap.ContainsKey(tileCoords)
                && !TileEngine.TileMap[tileCoords].IsBlocking;

            return canbeMoved;
        }

        /// <summary>
        /// Checks the 8 tiles around where the player is, and returns the first creature (if any) found.
        /// </summary>
        /// <param name="playerTileCoords">The coordinates of the player's current tile</param>
        /// <returns></returns>
        private CreatureEntity GetFirstCreatureInMeleeRange(Vector2 playerTileCoords)
        {
            Tile playerTile = TileEngine.TileMap[playerTileCoords];
            CreatureEntity creature;

            Tile n, ne, e, se, s, sw, w, nw;
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X, playerTileCoords.Y-1), out n);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X+1, playerTileCoords.Y-1), out ne);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X+1, playerTileCoords.Y), out e);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X+1, playerTileCoords.Y+1), out se);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X, playerTileCoords.Y+1), out s);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X-1, playerTileCoords.Y+1), out sw);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X-1, playerTileCoords.Y), out w);
            TileEngine.TileMap.TryGetValue(new Vector2(playerTileCoords.X-1, playerTileCoords.Y-1), out nw);

            if (n != null)
            {
                creature = TryGetCreatureInTile(n);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (ne != null)
            {
                creature = TryGetCreatureInTile(ne);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (e != null)
            {
                creature = TryGetCreatureInTile(e);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (se != null)
            {
                creature = TryGetCreatureInTile(se);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (s != null)
            {
                creature = TryGetCreatureInTile(s);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (sw != null)
            {
                creature = TryGetCreatureInTile(sw);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (w != null)
            {
                creature = TryGetCreatureInTile(w);
                if (creature != null)
                {
                    return creature;
                }
            }

            if (nw != null)
            {
                creature = TryGetCreatureInTile(nw);
                if (creature != null)
                {
                    return creature;
                }
            }

            return null;
        }

        /// <summary>
        /// Gets the first creature entity on the tile, if any.
        /// Otherwise returns null.
        /// </summary>
        private CreatureEntity TryGetCreatureInTile(Vector2 tileCoords)
        {
            Tile tile = TileEngine.TileMap[tileCoords];
            return tile.Objects.OfType<CreatureEntity>().Select(entity => entity as CreatureEntity).FirstOrDefault();
        }

        /// <summary>
        /// Gets the first creature entity on the tile, if any.
        /// Otherwise returns null.
        /// </summary>
        private CreatureEntity TryGetCreatureInTile(Tile tile)
        {
            return tile.Objects.OfType<CreatureEntity>().Select(entity => entity as CreatureEntity).FirstOrDefault();
        }

        /// <summary>
        /// Gets the coordinates of the tile the player would move to,
        /// if the player moved dx tiles on the x-axis and dy tiles on the y-axis.
        /// </summary>
        /// <param name="dx">The number of tiles the player would move on the x-axis</param>
        /// <param name="dy">The number of tiles the player would move on the y-axis</param>
        /// <returns></returns>
        private Vector2 GetNextPlayerTileCoords(int dx, int dy)
        {
            return new Vector2(Player.Coords.X + dx, Player.Coords.Y + dy);
        }

        #endregion

        /// <summary>
        /// Gets the tile the player is currently on.
        /// </summary>
        /// <returns></returns>
        public Tile GetPlayerTile()
        {
            return TileEngine.TileMap[Player.Coords];
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            TileEngine.Update(gameTime);

            Player.Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// Determines how the panel is drawn.
        /// </summary>
        public override void Draw(SpriteBatch spriteBatch)
        {
            // Draw the background
            Utils.DrawFilledRectangle(BackgroundColor, SizeAndShape, spriteBatch, Arroguella.GraphicsDevice);

            // Compute the FOV
            //TileEngine.LightCircle(Player.Coords, Player.FovRadius, Player.LightIntensity);
            TileEngine.LightCircle(Player.Coords, Player.FovRadius, Player.LightIntensity, Player.LightIntensity, Player.LightIntensity/2);

            // Draw the tiles
            TileEngine.Draw(spriteBatch);            

            // Draw the Player            
            Player.Draw(spriteBatch);
        }

        /// <summary>
        /// Gets the tile at the specified x,y coordinates in pixels.
        /// </summary>
        /// <returns>The tile, if found. Otherwise, null.</returns>
        public Tile GetTileAtPixelCoordinates(int px, int py)
        {
            return GetTileAtCoordinates(TileEngine.ConvertPixelCoordinatesToTileCoordinates(px, py));
        }

        /// <summary>
        /// Gets the tile at the specified tile coordinates.
        /// </summary>
        /// <returns>The tile, if found. Otherwise null.</returns>
        public Tile GetTileAtCoordinates(Vector2 tileCoordinates)
        {
            if (TileEngine.TileMap.ContainsKey(tileCoordinates))
            {
                return TileEngine.TileMap[tileCoordinates];
            }

            return null;
        }        

        #endregion

        #region Properties

        public Player Player;

        public TileEngine TileEngine;

        public CombatEngine CombatEngine;

        internal Guid Seed { get; set; }

        #endregion
    }
}
